/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the GLQuake source code.

 The GLQuake source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The GLQuake source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the GLQuake source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// cg_main.cpp - main client-game file
//


#include "cg_local.h"


cg_t						cg;
cgs_t						cgs;

etClient_t *				etClient;

cvar_t *					cg_showSnapshots;
cvar_t *					cg_showDeltaEntities;
cvar_t *					cg_showPredictionMiss;
cvar_t *					cg_showEvents;
cvar_t *					cg_skipPrediction;
cvar_t *					cg_skipRendering;
cvar_t *					cg_predictionErrorDecay;
cvar_t *					cg_debugMove;
cvar_t *					cg_testModelSpawn;
cvar_t *					cg_testModelPitch;
cvar_t *					cg_testModelYaw;
cvar_t *					cg_testModelRoll;
cvar_t *					cg_testModelRotatePitch;
cvar_t *					cg_testModelRotateYaw;
cvar_t *					cg_testModelRotateRoll;
cvar_t *					cg_testModelAnimate;
cvar_t *					cg_testGunX;
cvar_t *					cg_testGunY;
cvar_t *					cg_testGunZ;
cvar_t *					cg_viewSize;
cvar_t *					cg_fov;
cvar_t *					cg_hand;
cvar_t *					cg_gunX;
cvar_t *					cg_gunY;
cvar_t *					cg_gunZ;
cvar_t *					cg_crosshair;
cvar_t *					cg_crosshairX;
cvar_t *					cg_crosshairY;
cvar_t *					cg_crosshairSize;
cvar_t *					cg_crosshairColor;
cvar_t *					cg_crosshairAlpha;
cvar_t *					cg_crosshairHealth;
cvar_t *					cg_drawGun;
cvar_t *					cg_drawHUD;
cvar_t *					cg_drawCrosshair;
cvar_t *					cg_draw2D;


/*
 ==================
 CG_PlayBackgroundTrack
 ==================
*/
static void CG_PlayBackgroundTrack (){

	char	name[MAX_PATH_LENGTH];

	Str_SPrintf(name, sizeof(name), "music/track%02i.ogg", cgs.musicTrack);

	// Play the music
	glqSound->PlayMusic(name, name, 0);
}


/*
 ==============================================================================

 FRAMEWORK

 ==============================================================================
*/


/*
 ==================
 CG_RunFrame
 ==================
*/
static void CG_RunFrame (int time, const userCmd_t *userCmd){

	int		msec;

	msec = time - cg.time;

	cg.oldTime = cg.time;
	cg.time = time;

	cg.frameTime = MS2SEC(msec);

	// If we are loading the level, do nothing
	if (cgs.loading)
		return;

	// Set up cg.snapshot and possibly cg.nextSnapshot
	CG_ProcessSnapshots();

	// If we haven't received any snapshots yet, do nothing
	if (!cg.snapshot)
		return;

	// Update the lagometer's frame graph
	CG_UpdateFrameGraph(cg.time - cgs.snapshots[cgs.latestSnapshot & PACKET_MASK].time);

	// Set up the active entities
	CG_SetupEntities();

	// Set up the prediction entities
	CG_SetupPredictEntities();

	// Predict the current player state
	CG_PredictPlayerState();

	// Update the active entities
	CG_UpdateEntities();

	// Update the local player entity
	CG_UpdatePlayerEntity();

	// Check for entity events
	CG_CheckEntityEvents();
}

/*
 ==================
 CG_Redraw
 ==================
*/
static void CG_Redraw (){

	if (cg_skipRendering->integerValue)
		return;

	// If we are loading the level or we haven't received any snapshots yet,
	// draw the loading screen
	if (cgs.loading || !cg.snapshot){
		CG_DrawLoading();
		return;
	}

	// Render the game view
	CG_RenderView();

	// Draw all the 2D elements
	CG_Draw2D();
}


/*
 ==============================================================================

 INITIALIZATION & SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 
 ==================
*/
static void CG_Init (bool demoPlaying, int clientNum, int serverMessageSequence){

	// Initialize game
	G_Init(true);

	// Register variables
	cg_showSnapshots = glqCVarSystem->Register("cg_showSnapshots", "0", CVAR_BOOL, CVAR_CHEAT | CVAR_GAME, "Show snapshots", 0, 0);
	cg_showDeltaEntities = glqCVarSystem->Register("cg_showDeltaEntities", "0", CVAR_BOOL, CVAR_CHEAT | CVAR_GAME, "Show delta entities", 0, 0);
	cg_showPredictionMiss = glqCVarSystem->Register("cg_showPredictionMiss", "0", CVAR_BOOL, CVAR_CHEAT | CVAR_GAME, "Show prediction misses", 0, 0);	
	cg_showEvents = glqCVarSystem->Register("cg_showEvents", "0", CVAR_BOOL, CVAR_CHEAT | CVAR_GAME, "Show entity events", 0, 0);	
	cg_skipPrediction = glqCVarSystem->Register("cg_skipPrediction", "0", CVAR_BOOL, CVAR_CHEAT | CVAR_GAME, "Skip movement prediction", 0, 0);	
	cg_skipRendering = glqCVarSystem->Register("cg_skipRendering", "0", CVAR_BOOL, CVAR_CHEAT | CVAR_GAME, "Skip all game rendering", 0, 0);
	cg_predictionErrorDecay = glqCVarSystem->Register("cg_predictionErrorDecay", "100", CVAR_INTEGER, CVAR_CHEAT | CVAR_GAME, "Prediction error decay time", 0, 1000);
	cg_debugMove = glqCVarSystem->Register("cg_debugMove", "0", CVAR_BOOL, CVAR_CHEAT | CVAR_GAME, "Debug player movement", 0, 0);	
	cg_testModelSpawn = glqCVarSystem->Register("cg_testModelSpawn", "0", CVAR_INTEGER, CVAR_CHEAT | CVAR_GAME, "Test model spawn (0 = floating, 1 = on ground, 2 = on hit surface)", 0, 2);
	cg_testModelPitch = glqCVarSystem->Register("cg_testModelPitch", "0.0", CVAR_FLOAT, CVAR_CHEAT | CVAR_GAME, "Test model pitch", 0.0f, 360.0f);
	cg_testModelYaw = glqCVarSystem->Register("cg_testModelYaw", "0.0", CVAR_FLOAT, CVAR_CHEAT | CVAR_GAME, "Test model yaw", 0.0f, 360.0f);
	cg_testModelRoll = glqCVarSystem->Register("cg_testModelRoll", "0.0", CVAR_FLOAT, CVAR_CHEAT | CVAR_GAME, "Test model roll", 0.0f, 360.0f);
	cg_testModelRotatePitch = glqCVarSystem->Register("cg_testModelRotatePitch", "0.0", CVAR_FLOAT, CVAR_CHEAT | CVAR_GAME, "Test model rotation pitch", 0.0f, 0.0f);
	cg_testModelRotateYaw = glqCVarSystem->Register("cg_testModelRotateYaw", "0.0", CVAR_FLOAT, CVAR_CHEAT | CVAR_GAME, "Test model rotation yaw", 0.0f, 0.0f);
	cg_testModelRotateRoll = glqCVarSystem->Register("cg_testModelRotateRoll", "0.0", CVAR_FLOAT, CVAR_CHEAT | CVAR_GAME, "Test model rotation roll", 0.0f, 0.0f);
	cg_testModelAnimate = glqCVarSystem->Register("cg_testModelAnimate", "0", CVAR_FLOAT, CVAR_CHEAT | CVAR_GAME, "Test model animation", 0.0f, 100.0f);
	cg_testGunX = glqCVarSystem->Register("cg_testGunX", "0.0", CVAR_FLOAT, CVAR_CHEAT | CVAR_GAME, "Test gun X offset", 0.0f, 0.0f);
	cg_testGunY = glqCVarSystem->Register("cg_testGunY", "0.0", CVAR_FLOAT, CVAR_CHEAT | CVAR_GAME, "Test gun Y offset", 0.0f, 0.0f);
	cg_testGunZ = glqCVarSystem->Register("cg_testGunZ", "0.0", CVAR_FLOAT, CVAR_CHEAT | CVAR_GAME, "Test gun Z offset", 0.0f, 0.0f);	
	cg_viewSize = glqCVarSystem->Register("cg_viewSize", "100", CVAR_INTEGER, CVAR_CHEAT | CVAR_GAME, "Size of the game view", 10, 100);
	cg_fov = glqCVarSystem->Register("cg_fov", Str_FromFloat(DEFAULT_FOV, -1), CVAR_FLOAT, CVAR_ARCHIVE | CVAR_GAME, "Field of view", 60.0f, 120.0f);	
	cg_hand = glqCVarSystem->Register("cg_hand", "2", CVAR_INTEGER, CVAR_ARCHIVE | CVAR_CHEAT | CVAR_GAME, "Gun handness (0 = left, 1 = center, 2 = right)", 0, 2);
	cg_gunX = glqCVarSystem->Register("cg_gunX", "0.0", CVAR_FLOAT, CVAR_ARCHIVE | CVAR_CHEAT | CVAR_GAME, "Gun X offset", 0.0f, 0.0f);
	cg_gunY = glqCVarSystem->Register("cg_gunY", "-3.0", CVAR_FLOAT, CVAR_ARCHIVE | CVAR_CHEAT | CVAR_GAME, "Gun Y offset", 0.0f, 0.0f);
	cg_gunZ = glqCVarSystem->Register("cg_gunZ", "3.0", CVAR_FLOAT, CVAR_ARCHIVE | CVAR_CHEAT | CVAR_GAME, "Gun Z offset", 0.0f, 0.0f);	
	cg_crosshair = glqCVarSystem->Register("cg_crosshair", "1", CVAR_INTEGER, CVAR_ARCHIVE | CVAR_GAME, "Select which crosshair to draw", 0, NUM_CROSSHAIRS);
	cg_crosshairX = glqCVarSystem->Register("cg_crosshairX", "0.0", CVAR_FLOAT, CVAR_ARCHIVE | CVAR_GAME, "Crosshair X offset", 0.0f, SCREEN_WIDTH);
	cg_crosshairY = glqCVarSystem->Register("cg_crosshairY", "0.0", CVAR_FLOAT, CVAR_ARCHIVE | CVAR_GAME, "Crosshair Y offset", 0.0f, SCREEN_HEIGHT);
	cg_crosshairSize = glqCVarSystem->Register("cg_crosshairSize", "24.0", CVAR_FLOAT, CVAR_ARCHIVE | CVAR_GAME, "Crosshair size", 0, 32);
	cg_crosshairColor = glqCVarSystem->Register("cg_crosshairColor", "7", CVAR_INTEGER, CVAR_ARCHIVE | CVAR_GAME, "Crosshair color index", 0, 63);
	cg_crosshairAlpha = glqCVarSystem->Register("cg_crosshairAlpha", "1.0", CVAR_FLOAT, CVAR_ARCHIVE | CVAR_GAME, "Crosshair translucency level", 0.0f, 1.0f);
	cg_crosshairHealth = glqCVarSystem->Register("cg_crosshairHealth", "0", CVAR_BOOL, CVAR_ARCHIVE | CVAR_GAME, "Color crosshair based on health", 0, 0);
	cg_drawGun = glqCVarSystem->Register("cg_drawGun", "1", CVAR_BOOL, CVAR_ARCHIVE | CVAR_GAME, "Draw the gun", 0, 0);
	cg_drawHUD = glqCVarSystem->Register("cg_drawHUD", "1", CVAR_BOOL, CVAR_ARCHIVE | CVAR_GAME, "Draw the status bar HUD", 0, 0);
	cg_drawCrosshair = glqCVarSystem->Register("cg_drawCrosshair", "1", CVAR_BOOL, CVAR_ARCHIVE | CVAR_GAME, "Draws a crosshair", 0, 0);
	cg_draw2D = glqCVarSystem->Register("cg_draw2D", "1", CVAR_BOOL, CVAR_ARCHIVE | CVAR_GAME, "Draw 2D elements on screen", 0, 0);

	// Add commands
	CG_AddCommands();

	// Save information
	cgs.demoPlaying = demoPlaying;

	cgs.clientNum = clientNum;

	cgs.latestSnapshot = serverMessageSequence;
	cgs.processedSnapshot = serverMessageSequence;

	// Allocate the entities
	cgs.entities = (entity_t *)glqMemory->ClearedAlloc(MAX_ENTITIES * sizeof(entity_t), TAG_CLIENT);

	// Reset input parameters
	etClient->SetSensitivityScale(1.0f);

	// Initialize particles
	CG_InitParticles();

	// Initialize FXs
	CG_InitFXs();
}

/*
 ==================
 CG_Shutdown
 ==================
*/
static void CG_Shutdown (){

	// Free the level
	CG_FreeLevel();

	// Shutdown FXs
	CG_ShutdownFXs();

	// Shutdown particles
	CG_ShutdownParticles();

	// Remove commands
	CG_RemoveCommands();

	// Shutdown game
	G_Shutdown(true);
}


// ============================================================================


/*
 ==================
 
 This is the only function exported by the game module
 ==================
*/
glqCGame_t *GetCGameAPI (etClient_t *import){

	static glqCGame_t	glqCGame;

	etClient						= import;

	glqCommon						= &import->glqCommon;
	glqMemory						= &import->glqMemory;
	glqFileSystem					= &import->glqFileSystem;
	glqCmdSystem					= &import->glqCmdSystem;
	glqCVarSystem					= &import->glqCVarSystem;
	glqMsgSystem					= &import->glqMsgSystem;
	glqDefManager					= &import->glqDefManager;
	glqParser						= &import->glqParser;
	glqSystem						= &import->glqSystem;
	glqTable						= &import->glqTable;
	glqCollision					= &import->glqCollision;
	glqRenderer						= &import->glqRenderer;
	glqSound						= &import->glqSound;
	glqGUI							= &import->glqGUI;

	// Set up the exported functions
	glqCGame.apiVersion				= CGAME_API_VERSION;

	glqCGame.ParseGameState			= CG_ParseGameState;
	glqCGame.ParseSnapshot			= CG_ParseSnapshot;
	glqCGame.ServerCommand			= CG_ServerCommand;
	glqCGame.LoadLevel				= CG_LoadLevel;
	glqCGame.FreeLevel				= CG_FreeLevel;
	glqCGame.PlayBackgroundTrack	= CG_PlayBackgroundTrack;
	glqCGame.RunFrame				= CG_RunFrame;
	glqCGame.Redraw					= CG_Redraw;
	glqCGame.Init					= CG_Init;
	glqCGame.Shutdown				= CG_Shutdown;

	return &glqCGame;
}